<!DOCTYPE html>
<html>

<head>
  <title>QR Code Correction</title>
  <style>
    .container {
      margin: 20px;
      display: flex;
      flex-direction: column;
      gap: 15px;
    }

    .canvas-container {
      display: flex;
      gap: 20px;
    }


    .controls {
      margin-bottom: 15px;
    }

    input[type="file"] {
      padding: 8px;
      background: #f0f0f0;
      border-radius: 4px;
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="controls">
      <input type="file" id="imageInput" accept="image/*">
    </div>
    <div class="canvas-container">
      <div>
        <h3>Original Image</h3>
        <canvas id="sourceCanvas"></canvas>
      </div>
      <div>
        <h3>Corrected QR</h3>
        <canvas id="correctedCanvas"></canvas>
      </div>
      <div>
        <h3> QR</h3>
        <canvas id="qrCanvas"></canvas>
      </div>
    </div>
  </div>

  <!-- 引入 jsQR 库 -->
  <script src="../../plugins/jsQR.js"></script>

  <script>
    // 生成高斯核
    function generateGaussianKernel (size, sigma) {
      const kernel = [];
      const center = Math.floor(size / 2);
      let sum = 0;
      for (let y = 0; y < size; y++) {
        kernel[y] = [];
        for (let x = 0; x < size; x++) {
          const dx = x - center;
          const dy = y - center;
          const value = Math.exp(-(dx * dx + dy * dy) / (2 * sigma * sigma));
          kernel[y][x] = value;
          sum += value;
        }
      }
      // 归一化
      for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
          kernel[y][x] /= sum;
        }
      }
      return kernel;
    }

    // 应用模糊处理（RGBA 图像）
    function applyGaussianBlur (imageData, kernel) {
      const { data, width, height } = imageData;
      const output = new Uint8ClampedArray(data.length);
      const kSize = kernel.length;
      const kHalf = Math.floor(kSize / 2);

      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          let r = 0, g = 0, b = 0, a = 0;
          for (let ky = -kHalf; ky <= kHalf; ky++) {
            for (let kx = -kHalf; kx <= kHalf; kx++) {
              const px = Math.min(width - 1, Math.max(0, x + kx));
              const py = Math.min(height - 1, Math.max(0, y + ky));
              const idx = (py * width + px) * 4;
              const weight = kernel[ky + kHalf][kx + kHalf];
              r += data[idx] * weight;
              g += data[idx + 1] * weight;
              b += data[idx + 2] * weight;
              a += data[idx + 3] * weight;
            }
          }
          const i = (y * width + x) * 4;
          output[i] = r;
          output[i + 1] = g;
          output[i + 2] = b;
          output[i + 3] = a;
        }
      }

      return new ImageData(output, width, height);
    }


    const imageInput = document.getElementById('imageInput');
    const sourceCanvas = document.getElementById('sourceCanvas');
    const correctedCanvas = document.getElementById('correctedCanvas');
    const qrCanvas = document.getElementById('qrCanvas');

    const sourceCtx = sourceCanvas.getContext('2d');
    const correctedCtx = correctedCanvas.getContext('2d');
    const qrCtx = qrCanvas.getContext('2d');

    let originalImage = null;

    // 处理文件选择
    imageInput.addEventListener('change', function (e) {
      const file = e.target.files[0];
      if (!file) return;

      const reader = new FileReader();
      reader.onload = function (event) {
        originalImage = new Image();
        originalImage.onload = processImage;
        originalImage.src = event.target.result;
      };
      reader.readAsDataURL(file);
    });

    function calculateSharpness (imageData) {
      // 转换为灰度数据
      const grayData = new Uint8Array(imageData.width * imageData.height);
      for (let i = 0, j = 0; i < imageData.data.length; i += 4, j++) {
        grayData[j] = 0.299 * imageData.data[i] + 0.587 * imageData.data[i + 1] + 0.114 * imageData.data[i + 2];
      }

      // 使用现有laplacian函数
      return laplacian(grayData, imageData.width, imageData.height);
    }

    function laplacian (gray, width, height) {
      let sum = 0, sum2 = 0, N = width * height;
      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          let L = 0;
          if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
            L = -4 * gray[y * width + x]
              + gray[y * width + (x - 1)]
              + gray[y * width + (x + 1)]
              + gray[(y - 1) * width + x]
              + gray[(y + 1) * width + x];
          }
          sum += L;
          sum2 += L * L;
        }
      }
      const mean = sum / N;
      return sum2 / N - mean * mean;
    }

    // 处理图片加载
    function processImage () {
      // 设置Canvas尺寸为图片原始尺寸
      sourceCanvas.width = originalImage.naturalWidth;
      sourceCanvas.height = originalImage.naturalHeight;

      correctedCanvas.width = originalImage.naturalWidth;
      correctedCanvas.height = originalImage.naturalHeight;

      sourceCtx.clearRect(0, 0, sourceCanvas.width, sourceCanvas.height);


      sourceCtx.drawImage(originalImage, 0, 0);

      try {
        // 获取二维码位置信息
        const imageData = sourceCtx.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height);

        const blurredImageData = applyGaussianBlur(imageData, generateGaussianKernel(5, 1.2));

        sourceCtx.putImageData(blurredImageData, 0, 0)

        const qrResult = jsQR(blurredImageData.data, blurredImageData.width, blurredImageData.height);

        console.log('qrResult.location', blurredImageData, qrResult);

        if (!qrResult) {
          throw new Error('未检测到二维码！');
        }


        console.time()

        //高斯模糊 
        processQRCorrection(imageData, qrResult.location);
        console.timeEnd()
      } catch (error) {
        console.error('图片处理出错:', error);
        alert(error.message);
      }
    }


    /**
     * 计算二维码的旋转角度和剪切角度
     * @params location  JsQR获取的坐标
     * @params width  图片宽度
     * @params height 图片高度
     * @returns {radian, horizontalSkewAngle, verticalSkewAngle, codeSize, correctedTopLeft}
     */
    function calculateSkewAngles (location, width, height) {
      const { topLeftCorner: tl, topRightCorner: tr, bottomLeftCorner: bl, bottomRightCorner: br } = location;


      // 1. 向量
      const dx1 = tr.x - tl.x;
      const dy1 = tr.y - tl.y;
      const dx2 = bl.x - tl.x;
      const dy2 = bl.y - tl.y;
      // 2.矫正首定位标点 

      // 3. 旋转角 φ（**弧度**）
      const radian = Math.atan2(dy1, dx1);
      // 2. 大小

      const L1 = Math.hypot(dx1, dy1);
      const L2 = Math.hypot(dx2, dy2);
      const qrSize = (L1 + L2) / 2;


      // 4. 构造仿射矩阵 M
      const m00 = dx1 / qrSize, m10 = dy1 / qrSize;
      const m01 = dx2 / qrSize, m11 = dy2 / qrSize;
      // 5. Rᵀ · M
      const cosφ = Math.cos(radian), sinφ = Math.sin(radian);
      const a_p = cosφ * m00 + sinφ * m10;
      const b_p = cosφ * m01 + sinφ * m11;
      const c_p = -sinφ * m00 + cosφ * m10;
      const d_p = -sinφ * m01 + cosφ * m11;
      // 6. 剪切角度 (仍然以度为单位)
      const horizontalSkewAngle = Math.atan2(b_p, a_p) * 180 / Math.PI;
      const verticalSkewAngle = Math.atan2(c_p, d_p) * 180 / Math.PI;
      // 7. 校正 tl
      const cx = width / 2, cy = height / 2;
      const cosm = Math.cos(-radian), sinm = Math.sin(-radian);
      const dx = tl.x - cx, dy = tl.y - cy;
      const corrX = dx * cosm - dy * sinm + cx;
      const corrY = dx * sinm + dy * cosm + cy;
      return {
        rotationRadian: radian,
        horizontalSkewAngle,
        verticalSkewAngle,
        qrSize,
        correctedTopLeft: { x: corrX, y: corrY }
      };
    }

    /*
 * calculateQRCorrection
 * 计算 QR 码的位置校正信息，包括旋转角度、倾斜角度、估算尺寸以及校正后左上角坐标。
 * @param {Object} location - 探测到的 QR 四个角坐标
 * @param {Number} imageWidth - 图像宽度
 * @param {Number} imageHeight - 图像高度
 * @returns {Object} 校正结果对象，包含 rotationRadian, verticalSkew, horizontalSkew, qrSize, maxTopLeft, correctedTopLeft
 */
    function calculateQRCorrection (location, imageWidth, imageHeight) {
      // 提取四个角点
      const tl = location.topLeftCorner;      // 左上角
      const tr = location.topRightCorner;     // 右上角
      const bl = location.bottomLeftCorner;   // 左下角
      const br = location.bottomRightCorner;  // 右下角

      // 图像中心点，用于旋转操作的参考点
      const center = { x: imageWidth / 2, y: imageHeight / 2 };

      // 1. 计算顶部边的旋转角度（以弧度为单位）
      const dxTop = tr.x - tl.x;  // 顶部边水平分量
      const dyTop = tr.y - tl.y;  // 顶部边垂直分量
      const rotationRadian = Math.atan2(dyTop, dxTop);  // 旋转角度 = 顶边与水平线夹角

      /**
       * 将点 p 绕 pivot 点按 angle 旋转
       * @param {Object} p - 待旋转点 {x, y}
       * @param {Number} angle - 旋转角度（弧度）
       * @param {Object} pivot - 旋转中心 {x, y}
       * @returns {Object} 旋转后坐标
       */
      const rotatePoint = (p, angle, pivot) => {
        const s = Math.sin(angle);
        const c = Math.cos(angle);
        const x = p.x - pivot.x;
        const y = p.y - pivot.y;
        return {
          x: x * c - y * s + pivot.x,
          y: x * s + y * c + pivot.y
        };
      };

      // 2. 对所有角点做反方向旋转（去除旋转影响），方便计算倾斜和尺寸
      const negRotation = -rotationRadian;
      const points = [tl, tr, bl, br];
      const [tl_R, tr_R, bl_R, br_R] = points.map(p => rotatePoint(p, negRotation, center));

      // 3. 将坐标系原点平移到 tl_R（去除平移影响）
      const offsetX = tl_R.x;
      const offsetY = tl_R.y;
      const tl_R_adj = { x: 0, y: 0 };
      const tr_R_adj = { x: tr_R.x - offsetX, y: tr_R.y - offsetY };
      const bl_R_adj = { x: bl_R.x - offsetX, y: bl_R.y - offsetY };
      const br_R_adj = { x: br_R.x - offsetX, y: br_R.y - offsetY };

      // 4. 计算水平和垂直方向的倾斜角度（skew）
      // horizontalSkew: 左侧边相对于垂直线的角度
      const horizontalSkew = Math.atan2(bl_R_adj.x, bl_R_adj.y);
      // verticalSkew: 底部边相对于水平线的角度
      const verticalSkew = Math.atan2(br_R_adj.y - bl_R_adj.y, br_R_adj.x - bl_R_adj.x);

      // 5. 根据倾斜角度估算 QR 码边长
      const cosVSkew = Math.cos(verticalSkew);
      const cosHSkew = Math.cos(horizontalSkew);
      let qrSize;

      if (cosVSkew > 0.0001 && cosHSkew > 0.0001) {
        // 双向倾斜都存在时，取顶部宽度和左侧高度的平均
        const s1 = tr_R_adj.x / cosVSkew;
        const s2 = bl_R_adj.y / cosHSkew;
        qrSize = (Math.abs(s1) + Math.abs(s2)) / 2;
      } else if (cosHSkew > 0.0001) {
        // 只有水平倾斜有效
        qrSize = Math.abs(bl_R_adj.y / cosHSkew);
      } else if (cosVSkew > 0.0001) {
        // 只有垂直倾斜有效
        qrSize = Math.abs(tr_R_adj.x / cosVSkew);
      } else {
        // 倾斜角度极小，直接取宽高平均
        qrSize = (Math.abs(tr_R_adj.x) + Math.abs(bl_R_adj.y)) / 2;
      }

      // 检查对角线宽度，取最大值以防错估
      const symmetricWidth1 = Math.abs(br_R_adj.x - tl_R_adj.x);
      const symmetricWidth2 = Math.abs(bl_R_adj.x - tr_R_adj.x);
      const maxSymmetricWidth = Math.max(symmetricWidth1, symmetricWidth2);
      if (maxSymmetricWidth > qrSize) {
        qrSize = maxSymmetricWidth;
      }

      // 6. 计算原图坐标系中，未校正区域左上角的最大可能位置
      const minX = Math.min(tl_R_adj.x, tr_R_adj.x, bl_R_adj.x, br_R_adj.x);
      const minY = Math.min(tl_R_adj.y, tr_R_adj.y, bl_R_adj.y, br_R_adj.y);
      const minVec = { x: minX + offsetX, y: minY + offsetY };
      const maxTopLeft = rotatePoint(minVec, rotationRadian, center);

      // 7. 在去旋转后的坐标系中，相对于原 tl 的校正后左上角位置
      const correctedTopLeft = {
        x: tl_R.x + minX,
        y: tl_R.y + minY
      };

      // 返回所有计算结果
      return {
        rotationRadian,    // 旋转弧度
        verticalSkew,      // 垂直倾斜弧度
        horizontalSkew,    // 水平倾斜弧度
        qrSize,            // 估算的 QR 边长（像素）
        maxTopLeft,        // 未校正区域在原图中的最大左上坐标
        correctedTopLeft   // 校正后区域左上坐标（去旋转、平移）
      };
    }





    // 处理二维码矫正
    function processQRCorrection (imageData, location) {
      try {
        console.log('params', imageData, location);
        console.log('topLeftPoint', location.topLeftCorner);

        // const correctionParams = calculateSkewAngles(location, correctedCanvas.width, correctedCanvas.height)
        // const backupParams = calculateQRCorrection(location, correctedCanvas.width, correctedCanvas.height)


        console.log('correctionParams', correctionParams);
        console.log('backupParams', backupParams);



        applyCorrection(backupParams);
      } catch (error) {
        console.error('矫正失败:', error);
        alert('矫正过程中发生错误');
      }
    }

    // 应用矫正
    function applyCorrection (params) {
      const { rotationRadian, qrSize, correctedTopLeft } = params;

      // correctedCtx.clearRect(0, 0, correctedCanvas.width, correctedCanvas.height);
      correctedCtx.save();
      correctedCtx.translate(correctedCanvas.width / 2, correctedCanvas.height / 2);
      correctedCtx.rotate(-rotationRadian);
      // correctedCtx.putImageData(imageData, -correctedCanvas.width / 2, -correctedCanvas.height / 2, 0, 0, correctedCanvas.width, correctedCanvas.height);
      correctedCtx.drawImage(sourceCanvas, 0, 0, correctedCanvas.width, correctedCanvas.height, -correctedCanvas.width / 2, -correctedCanvas.height / 2, correctedCanvas.width, correctedCanvas.height);
      correctedCtx.restore();

      qrCanvas.width = qrSize;
      qrCanvas.height = qrSize;

      const imageData = correctedCtx.getImageData(correctedTopLeft.x, correctedTopLeft.y, qrCanvas.width, qrCanvas.height)

      console.log('清晰度：', imageData, calculateSharpness(imageData));

      qrCtx.putImageData(imageData, 0, 0,);
    }
  </script>
</body>

</html>